package com.mcflywzx.airhockey.objects;

import static android.opengl.GLES20.*;
import static android.opengl.GLES20.glDrawArrays;

import java.util.ArrayList;
import java.util.List;

import android.util.FloatMath;

import com.mcflywzx.airhockey.Constants;
import com.mcflywzx.airhockey.util.Geometry.Circle;
import com.mcflywzx.airhockey.util.Geometry.Cube;
import com.mcflywzx.airhockey.util.Geometry.Cylinder;
import com.mcflywzx.airhockey.util.Geometry.Point;

public class ObjectBuilder {
	static interface DrawCommand {
		void draw();
	}
	
	static class GeneratedData {
		final float[] vD;
		final List<DrawCommand> drawList;
		
		GeneratedData(float[] vD, List<DrawCommand> drawList) {
			this.vD = vD;
			this.drawList = drawList;
		}
	}
	
	private static final int sizeOfCubeInVertices = 30;
	
	private static final int FLOATS_PER_VERTEX = 3;
	private float[] vD;
	
	private final List<DrawCommand> drawList = new ArrayList<DrawCommand>();
	
	private int offset = 0;
	
	private ObjectBuilder(int sizeInVertices) {
		vD = new float[sizeInVertices * FLOATS_PER_VERTEX];
	}
	
	private ObjectBuilder() {
	}
	
	private static int sizeOfCircleInVertices(int numPoints) {
		return 1 + (numPoints + 1);
	}
	
	private static int sizeOfOpenCylinderInVertices(int numPoints) {
		return (numPoints + 1) * 2;
	}
	
	static GeneratedData createCube(Cube cube) {
		ObjectBuilder builder = new ObjectBuilder(10);
		builder.appendCube(cube);
		return builder.build();
	}
	
	static GeneratedData createCylinder(Cylinder cylinder, int numPoints) {
		int size = sizeOfCircleInVertices(numPoints) * 2 + sizeOfOpenCylinderInVertices(numPoints);
		
		ObjectBuilder builder = new ObjectBuilder(size);
		
		Circle Top = new Circle(cylinder.position.translateY(cylinder.h), cylinder.r);
		Circle Bottom = new Circle(cylinder.position, cylinder.r);
		builder.appendCircle(Top, numPoints);
		builder.appendCircle(Bottom, numPoints);
		builder.appendOpenCylinder(cylinder, numPoints);
		
		return builder.build();
	}
	
	static GeneratedData createMallet(Point center, float R, float H, int numPoints) {
		int size = sizeOfCircleInVertices(numPoints) * 2 + sizeOfOpenCylinderInVertices(numPoints) * 2;
		
		ObjectBuilder builder = new ObjectBuilder(size);
		
		//Frist, generate the mallet base.
		float baseH = H * 0.25f;
		
		Circle baseCircle = new Circle(
				center.translateY(-baseH),
				R);
		Cylinder baseCylinder = new Cylinder(
				baseCircle.center.translateY(-baseH / 2f),
				R, baseH);
		
		builder.appendCircle(baseCircle, numPoints);
		builder.appendOpenCylinder(baseCylinder, numPoints);
		
		
		//Than, generate the mallet Handle.
		float HandleH = H * 0.75f;
		float HandleR = R / 3f;
		
		Circle HandleCircle = new Circle(
				center.translateY(H * 0.5f),
				HandleR);
		Cylinder HandleCylinder = new Cylinder(
				HandleCircle.center.translateY(-HandleH / 2f),
				HandleR, HandleH);
		
		builder.appendCircle(HandleCircle, numPoints);
		builder.appendOpenCylinder(HandleCylinder, numPoints);
		
		return builder.build();
	}
	
	private void appendCircle(Circle circle, int numPoints) {
		final int startVertex = offset / FLOATS_PER_VERTEX;
		final int numVertices =  sizeOfCircleInVertices(numPoints);
		
		//Center point of fan
		vD[offset++] = circle.center.x;
		vD[offset++] = circle.center.y;
		vD[offset++] = circle.center.z;
		
		// Fan around center point.
		for(int i = 0; i <= numPoints; i++) {
			float angleInRadians = 
					((float) i / (float) numPoints)
				*	((float) Math.PI * 2f);
			
			vD[offset++] = 
					circle.center.x 
				+	circle.r * (float)Math.cos(angleInRadians);
			vD[offset++] = circle.center.y;
			vD[offset++] = 
					circle.center.z
				+	circle.r * (float)Math.sin(angleInRadians);
		}
		
		drawList.add(new DrawCommand() {
			@Override
			public void draw() {
				glDrawArrays(GL_TRIANGLE_FAN, startVertex, numVertices);
			}
		});
	}
	
	private void appendOpenCylinder(Cylinder cylinder, int numPoints) {

		final int startVertex = offset / FLOATS_PER_VERTEX;
		final int numVertices =  sizeOfOpenCylinderInVertices(numPoints);
		final float yStart = cylinder.position.y;
		final float yEnd = cylinder.position.y + cylinder.h;
		
		// Fan around center point.
		for(int i = 0; i <= numPoints; i++) {
			float angleInRadians = 
					((float) i / (float) numPoints)
				*	((float) Math.PI * 2f);
			
			float xPosition = 
					cylinder.position.x 
				+	cylinder.r * (float)Math.cos(angleInRadians);
			float zPosition = 
					cylinder.position.z 
				+	cylinder.r * (float)Math.sin(angleInRadians);
			
			vD[offset++] = xPosition;
			vD[offset++] = yStart;
			vD[offset++] = zPosition;
			
			vD[offset++] = xPosition;
			vD[offset++] = yEnd;
			vD[offset++] = zPosition;
		}
		
		drawList.add(new DrawCommand() {
			@Override
			public void draw() {
				glDrawArrays(GL_TRIANGLE_STRIP, startVertex, numVertices);
			}
		});
	}
	

	private void appendCube(Cube cube) {

		final int startVertex = offset / FLOATS_PER_VERTEX;
		final int numVertices =  10;
		
		final float xStart = cube.position.x - cube.L / 2;
		final float xEnd = cube.position.x + cube.L / 2;
		final float yStart = cube.position.y;
		final float yEnd = cube.position.y + cube.H;
		final float zStart = cube.position.z - cube.W / 2;
		final float zEnd = cube.position.z + cube.W / 2;
			
		vD = new float[]{
			cube.position.x, cube.position.y, cube.position.z,
			xStart, yStart, zStart,
			xEnd, yStart, zStart,
			xEnd, yStart, zEnd,
			xStart, yStart, zEnd,
			
			xStart, cube.position.y + cube.H / 2, cube.position.z,
			xStart, yStart, zStart,
			xStart, yStart, zEnd,
			xStart, yEnd, zEnd,
			xStart, yEnd, zStart
			
			
			
		};
		
		drawList.add(new DrawCommand() {
			@Override
			public void draw() {
				glDrawArrays(GL_TRIANGLE_FAN, startVertex, 5);
				glDrawArrays(GL_TRIANGLE_FAN, startVertex + 5, 5);
			}
		});
	}
	
	private GeneratedData build() {
		return new GeneratedData(vD, drawList);
	}
}
